﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Hive.Core.Model
{
    /// <summary>
    /// Describes how a binary repository has been configured.
    /// </summary>
    public abstract class BinaryRepositoryConfiguration : Configuration
    {
        private const string MappingKey = "Mapping";
        private readonly List<Mapping> mappings;

        /// <summary>
        /// Creates a binary repository configuration.
        /// </summary>
        protected BinaryRepositoryConfiguration()
        {
            mappings = new List<Mapping>();
        }

        /// <summary>
        /// Creates a binary repository configuration from a descriptor.
        /// </summary>
        /// <param name="descriptor">The descriptor.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="descriptor"/> is null.</exception>
        protected BinaryRepositoryConfiguration(Descriptor descriptor)
            : base(descriptor)
        {
            mappings = new List<Mapping>();

            foreach (Descriptor mappingDescriptor in descriptor.GetChildren(MappingKey))
                mappings.Add(Mapping.FromDescriptor(mappingDescriptor));
        }

        /// <summary>
        /// Creates the binary repository from the configuration.
        /// </summary>
        /// <returns>The binary repository.</returns>
        public abstract IBinaryRepository CreateBinaryRepository();

        /// <summary>
        /// Gets the read-only list of mappings.
        /// </summary>
        public IList<Mapping> Mappings
        {
            get { return new ReadOnlyCollection<Mapping>(mappings); }
        }

        /// <summary>
        /// Adds a mapping for artifact ids that match a pattern to an artifact path.
        /// </summary>
        /// <param name="artifactIdRegex">The regular expression for the artifact id.</param>
        /// <param name="artifactPathFormat">The format string from which to generate the
        /// repository rool url where '{package}' acts as a placeholder for the package name, '{branch}'
        /// acts as a placeholder for the branch name, and '{version}' acts as a placeholder for the version label.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="artifactIdRegex"/> or <paramref name="artifactPathFormat"/> is null.</exception>
        public void AddMapping(Regex artifactIdRegex, string artifactPathFormat)
        {
            AddMapping(new Mapping(artifactIdRegex, artifactPathFormat));
        }

        /// <summary>
        /// Adds a mapping for artifact ids that match a pattern to an artifact path.
        /// </summary>
        /// <param name="mapping">The mapping to add.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="mapping"/> is null.</exception>
        public void AddMapping(Mapping mapping)
        {
            if (mapping == null)
                throw new ArgumentNullException("mapping");

            mappings.Add(mapping);
        }

        /// <summary>
        /// Removes a mapping.
        /// </summary>
        /// <param name="mapping">The mapping to add.</param>
        /// <returns>True if the mapping previously existed and was removed.</returns>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="mapping"/> is null.</exception>
        public bool RemoveMapping(Mapping mapping)
        {
            if (mapping == null)
                throw new ArgumentNullException("mapping");

            return mappings.Remove(mapping);
        }

        /// <summary>
        /// Removes all mappings.
        /// </summary>
        public void ClearMappings()
        {
            mappings.Clear();
        }

        /// <summary>
        /// Gets the archive path for a particular package branch and version label, or null if not available.
        /// </summary>
        /// <param name="packageName">The package name.</param>
        /// <param name="branchName">The branch name.</param>
        /// <param name="versionLabel">The version label.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="packageName"/>,
        /// <paramref name="branchName"/> or <paramref name="versionLabel"/> is null.</exception>
        /// <returns>The archive path or null if not available.</returns>
        public string GetArchivePath(string packageName, string branchName, string versionLabel)
        {
            if (packageName == null)
                throw new ArgumentNullException("packageName");
            if (branchName == null)
                throw new ArgumentNullException("branchName");
            if (versionLabel == null)
                throw new ArgumentNullException("versionLabel");

            string artifactId = string.Concat(packageName, "/", branchName, "/", versionLabel);

            foreach (Mapping mapping in mappings)
            {
                if (mapping.ArtifactIdRegex.IsMatch(artifactId))
                {
                    return mapping.ArtifactPathFormat
                        .Replace("{package}", packageName)
                        .Replace("{branch}", branchName)
                        .Replace("{version}", versionLabel);
                }
            }

            return null;
        }

        /// <inheritdoc />
        protected override void PopulateDescriptor(Descriptor descriptor)
        {
            base.PopulateDescriptor(descriptor);

            foreach (var mapping in mappings)
                descriptor.AddChild(MappingKey, mapping.ToDescriptor());
        }

        /// <summary>
        /// Describes a binary repository mapping.
        /// </summary>
        public sealed class Mapping
        {
            private const string ArtifactIdRegexKey = "ArtifactIdRegex";
            private const string ArtifactPathFormatKey = "ArtifactPathFormat";

            private readonly Regex artifactIdRegex;
            private readonly string artifactPathFormat;

            /// <summary>
            /// Creates a mapping.
            /// </summary>
            /// <param name="artifactIdRegex">The regular expression for the artifact id.</param>
            /// <param name="artifactPathFormat">The format string from which to generate the
            /// repository rool url where '{package}' acts as a placeholder for the package name, '{branch}'
            /// acts as a placeholder for the branch name, and '{version}' acts as a placeholder for the version label.</param>
            /// <exception cref="ArgumentNullException">Thrown if <paramref name="artifactIdRegex"/> or <paramref name="artifactPathFormat"/> is null.</exception>
            public Mapping(Regex artifactIdRegex, string artifactPathFormat)
            {
                if (artifactIdRegex == null)
                    throw new ArgumentNullException("artifactIdRegex");
                if (artifactPathFormat == null)
                    throw new ArgumentNullException("artifactPathFormat");

                this.artifactIdRegex = artifactIdRegex;
                this.artifactPathFormat = artifactPathFormat;
            }

            /// <summary>
            /// Gets the regular expression for the artifact id.
            /// </summary>
            /// <remarks>
            /// <para>
            /// An artifact id is a string of the form "{package}/{branch}/{version}" that
            /// uniquely identifies a particular version of a binary artifact.
            /// Example: "myproject/trunk/1.0"
            /// </para>
            /// </remarks>
            public Regex ArtifactIdRegex
            {
                get { return artifactIdRegex; }
            }

            /// <summary>
            /// Gets or sets the archive path format.
            /// </summary>
            /// <value>
            /// The format string from which to generate the
            /// repository rool url where '{package}' acts as a placeholder for the package name, '{branch}'
            /// acts as a placeholder for the branch name, and '{version}' acts as a placeholder for the version label.
            /// </value>
            public string ArtifactPathFormat
            {
                get { return artifactPathFormat; }
            }

            internal static Mapping FromDescriptor(Descriptor descriptor)
            {
                string artifactIdRegex = descriptor.GetProperty(ArtifactIdRegexKey);
                if (artifactIdRegex == null)
                    throw new HiveException("Missing artifact id regex.");

                string artifactPathFormat = descriptor.GetProperty(ArtifactPathFormatKey);
                if (artifactPathFormat == null)
                    throw new HiveException("Missing artifact path format.");

                return new Mapping(new Regex(artifactIdRegex), artifactPathFormat);
            }

            internal Descriptor ToDescriptor()
            {
                var descriptor = new Descriptor();

                descriptor.AddProperty(ArtifactIdRegexKey, artifactIdRegex.ToString());
                descriptor.AddProperty(ArtifactPathFormatKey, artifactPathFormat);

                return descriptor;
            }
        }
    }
}
